﻿ #region Disclaimer/Info
 
 /////////////////////////////////////////////////////////////////////////////////////////////////
 //
 //   File:		RandomStrings.cs
 //   Website:		http://dexterblogengine.com/
 //   Authors:		http://dexterblogengine.com/About.ashx
 //   Rev:		1
 //   Created:		19/01/2011
 //   Last edit:		19/01/2011
 //   License:		GNU Library General Public License (LGPL)
 //   File:            RandomStrings.cs
 //   For updated news and information please visit http://dexterblogengine.com/
 //   Dexter is hosted to Codeplex at http://dexterblogengine.codeplex.com
 //   For any question contact info@dexterblogengine.com
 //
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #endregion
 
namespace Dexter.Core.Helpers
{
	#region Usings

	using System;
	using System.Collections.Generic;
	using System.Security.Cryptography;
	using System.Text;

	#endregion

	/// <summary>
	/// 	An utility class to generate random strings
	/// </summary>
	public class RandomStrings
	{
		private const Int32 DEFAULT_MINIMUM = 6;
		private const Int32 DEFAULT_MAXIMUM = 10;
		private const Int32 U_BOUND_DIGIT = 61;
		private static readonly RandomStrings instance = new RandomStrings ();
		private readonly List<Char> exclusions = new List<char> ();
		private readonly char[] pwdCharArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/?".ToCharArray ();
		private readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
		private Boolean allowRepeatCharacters = true;
		private Boolean allowSymbols = true;
		private Int32 maxLenght = DEFAULT_MAXIMUM;
		private Int32 minLenght = DEFAULT_MINIMUM;

		/// <summary>
		/// 	A list of char that must be excluded from the
		/// 	generated password
		/// </summary>
		/// <value>The exclusions.</value>
		public List<Char> Exclusions
		{
			get { return this.exclusions; }
		}

		/// <summary>
		/// 	Minimum char number of the generated password
		/// </summary>
		/// <value>The min lenght.</value>
		public Int32 MinLenght
		{
			get { return this.minLenght; }
			set
			{
				this.minLenght = value;
				if ( DEFAULT_MINIMUM > this.minLenght )
				{
					this.minLenght = DEFAULT_MINIMUM;
				}
			}
		}

		/// <summary>
		/// 	Maximum char number of the generated password
		/// </summary>
		/// <value>The max lenght.</value>
		public Int32 MaxLenght
		{
			get { return this.maxLenght; }
			set
			{
				this.maxLenght = value;
				if ( this.minLenght >= this.maxLenght )
				{
					this.maxLenght = DEFAULT_MAXIMUM;
				}
			}
		}

		/// <summary>
		/// 	Gets or sets a value indicating whether symbols are allowed.
		/// </summary>
		/// <value><c>true</c> if symbols are allowed; otherwise, <c>false</c>.</value>
		public Boolean AllowSymbols
		{
			get { return this.allowSymbols; }
			set { this.allowSymbols = value; }
		}

		/// <summary>
		/// 	If true the resulting string can contains
		/// 	equals chars.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if [allow repeat characters]; otherwise, <c>false</c>.
		/// </value>
		public Boolean AllowRepeatCharacters
		{
			get { return this.allowRepeatCharacters; }
			set { this.allowRepeatCharacters = value; }
		}

		/// <summary>
		/// 	If true the resulting string can contains
		/// 	consecutive equals chars.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if [allow consecutive characters]; otherwise, <c>false</c>.
		/// </value>
		public Boolean AllowConsecutiveCharacters { get; set; }

		/// <summary>
		/// 	Generates a random string using default settings.
		/// </summary>
		/// <returns></returns>
		public static String GenerateRandom ()
		{
			return instance.Next ();
		}

		/// <summary>
		/// 	Given 2 bound returns a random number between them
		/// </summary>
		/// <param name = "lBound">The lower bound</param>
		/// <param name = "uBound">The upper bound</param>
		/// <returns>The random result</returns>
		protected Int32 GetCryptographicRandomNumber ( Int32 lBound , Int32 uBound )
		{
			// Assumes lBound >= 0 && lBound < uBound
			// returns an Int32 >= lBound and < uBound
			UInt32 urndnum;
			Byte[] rndnum = new Byte[4];

			if ( lBound == uBound - 1 || lBound == uBound )
			{
				// test for degenerate case where only lBound can be returned
				return lBound;
			}

			UInt32 xcludeRndBase = ( UInt32.MaxValue - ( UInt32.MaxValue % ( UInt32 ) ( uBound - lBound ) ) );

			do
			{
				rng.GetBytes ( rndnum );
				urndnum = BitConverter.ToUInt32 ( rndnum , 0 );
			}
			while ( urndnum >= xcludeRndBase );

			return ( Int32 ) ( urndnum % ( uBound - lBound ) ) + lBound;
		}

		/// <summary>
		/// 	Return a random char
		/// </summary>
		/// <returns>The choosen char</returns>
		protected char GetRandomCharacter ()
		{
			Int32 upperBound = pwdCharArray.GetUpperBound ( 0 );

			if ( !this.AllowSymbols )
			{
				upperBound = U_BOUND_DIGIT;
			}

			Int32 randomCharPosition = GetCryptographicRandomNumber ( pwdCharArray.GetLowerBound ( 0 ) , upperBound );
			return pwdCharArray [ randomCharPosition ];
		}

		/// <summary>
		/// 	Generates one string based on the defined rules.
		/// </summary>
		/// <returns>The generated string.</returns>
		public String Next ()
		{
			// Pick random length between minimum and maximum   
			int pwdLength = GetCryptographicRandomNumber ( this.MinLenght , this.MaxLenght );

			StringBuilder pwdBuffer = new StringBuilder
			                          {
			                          	Capacity = this.MaxLenght
			                          };

			// Generate random characters
			char nextCharacter;
			// Initial dummy character flag
			char lastCharacter = nextCharacter = '\n';

			for ( int i = 0; i < pwdLength; i++ )
			{
				nextCharacter = GetRandomCharacter ();

				if ( !this.AllowConsecutiveCharacters )
				{
					while ( lastCharacter == nextCharacter )
					{
						nextCharacter = GetRandomCharacter ();
					}
				}

				if ( !this.AllowRepeatCharacters )
				{
					string temp = pwdBuffer.ToString ();
					int duplicateIndex = temp.IndexOf ( nextCharacter );
					while ( -1 != duplicateIndex )
					{
						nextCharacter = GetRandomCharacter ();
						duplicateIndex = temp.IndexOf ( nextCharacter );
					}
				}

				if ( this.Exclusions != null )
				{
					while ( this.Exclusions.Contains ( nextCharacter ) )
					{
						nextCharacter = GetRandomCharacter ();
					}
				}

				pwdBuffer.Append ( nextCharacter );
				lastCharacter = nextCharacter;
			}

			return pwdBuffer.ToString ();
		}
	}
}
